#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "remote.h"
#include "libusb.h"
#include "control.h"

static void strtolower(char *str);

#define _SAFE_WRITE(mPtr, mValue) \
	if (mPtr) { *mPtr = mValue; }

#define _LOAD_ADDR(mPORT, mPIN, mDDR, IDC) \
	_SAFE_WRITE(mPORT, PORT##IDC);\
	_SAFE_WRITE(mPIN,  PIN##IDC);\
	_SAFE_WRITE(mDDR,  DDR##IDC);

static int _writeRadioPackage(libusb_device_handle *dev, const char *buffer, int len, char *rspbuf, int *rsplen) {
	int ret;
	char result;
	
	ret = usb_ctrl_cmd(dev, U2WRQ_RADIO_WRITE, len, 0, (char *)buffer, len, 500, FALSE);
	
	if (ret != len) {
		printf("write radio package error\n");
		return -1;
	}
	
	// get the last transmit result
	ret = usb_ctrl_cmd(dev, U2WRQ_LAST_ERR, 0, 0, &result, 1, 100, TRUE);
	if (ret != sizeof(result)) {
		printf("get last transmit statu error\n");
		return -1;
	}
 	
 	switch(result) {
	case 1:
		printf("write timeout\n");
		return -1;
	case 2:
		printf("no response from the remote device\n");
		return -1;
	case 0:
		// everything is fine
		break;
	default:
		printf("unknown error\n");
		return -1;
	}
	
	// get the response data count
	usleep(1000);
	ret = usb_ctrl_cmd(dev, U2WRQ_RADIO_RCOUNT, 0, 0, &result, 1, 100, TRUE);
	if (ret != 1) {
		printf("get rasponse data count error\n");
		return -1;
	}
	
	*rsplen = result;
	// get the reaponse data
	if (result) {
		ret = usb_ctrl_cmd(dev, U2WRQ_RADIO_READ, result, 0, rspbuf, result, 500, TRUE);
		if (ret != result) {
			printf("get response data error\n");
			return -1;
		}
	}
	
	// printf("write package passed, get %d bytes response\n", result);
	return 0;
}

int writeRadioPackage(
	libusb_device_handle * dev,
	prx_request_t request, 
	int addr, 
	int mark, 
	int value0, 
	char *data, 
	int data_len,
	char *rspbuf,
	int *rsp_size ) {
	
	prx_package_t package;
	
	if (data_len > 26) {
		printf("too much data in one package.\n");
		return -1;
	}
	
	package.request = request;
	package.addrLsb = addr & 0xFF;
	package.addrMsb = (addr >> 8)	 & 0xFF;
	package.mark = mark;
	package.value0 = value0 & 0xFF;
	memcpy(package.data, data, data_len);
	package.datlen = data_len;
	// sent the package to the air
	return _writeRadioPackage(dev, (const char *)&package, sizeof(prx_package_t), rspbuf, rsp_size);
}

int radioPing(libusb_device_handle *dev) {
	int n;
	unsigned long ms[2];
	
	ms[0] = getTickCount();
	if (writeRadioPackage(dev, PRX_ECHO, 0, 0, 0, NULL, 0, NULL, &n) != 0) {
		printf("no response from remote device\n");
	} else {
		ms[1] = getTickCount();
		printf("ping remote device ok, delay=%ld ms\n", ms[1] - ms[0]);
	}
	return 0;
}

int radioIO8Bit(libusb_device_handle *dev, int reg_addr, int pinMask, int act) {
	char retbuf[32];
	int retlen;
	
	if(writeRadioPackage(dev, PRX_IO8_BIT, reg_addr, act, pinMask, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s IO8[0x%x] with mask[0x%x] error\n", act ? "set" : "clear", reg_addr & 0xFF, pinMask & 0xFF);
		return -1;
	}

	// set another dummy transfer to read last data, this's a wireless 'bug': the last payload is 
	// in the next package
	if(writeRadioPackage(dev, PRX_ECHO, 0, 0, 0x00, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s IO8[0x%x] with mask[0x%x] error\n", act ? "set" : "clear", reg_addr & 0xFF, pinMask & 0xFF);
		return -1;
	}

	printf("%s IO8[0x%x] with mask[0x%x], IO8[0x%x]=0x%x\n", act ? "set" : "clear", reg_addr & 0xFF, pinMask & 0xFF, reg_addr & 0xFFFF, retbuf[0] & 0xFF);
	return 0;
}

int radioMem8Bit(libusb_device_handle *dev, int reg_addr, int pinMask, int act) {
	char retbuf[32];
	int retlen;
	
	if(writeRadioPackage(dev, PRX_MEM8_BIT, reg_addr, act, pinMask, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s MEM8[0x%x] with mask[0x%x] error\n", act ? "set" : "clear", reg_addr & 0xFF, pinMask & 0xFF);
		return -1;
	}

	// set another dummy transfer to read last data, this's a wireless 'bug': the last payload is 
	// in the next package
	if(writeRadioPackage(dev, PRX_ECHO, 0, 0, 0x00, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s MEM8[0x%x] with mask[0x%x] error\n", act ? "set" : "clear", reg_addr & 0xFF, pinMask & 0xFF);
		return -1;
	}

	printf("%s MEM8[0x%x] with mask[0x%x], MEM8[0x%x]=0x%x\n", act ? "set" : "clear", reg_addr & 0xFF, pinMask & 0xFF, reg_addr & 0xFF, retbuf[0] & 0xFF);
	return 0;
}

int radioIO8RW(libusb_device_handle *dev, int reg_addr, int reg_value, int is_write) {
	int act;
	int pinMask;
	char retbuf[32];
	int retlen;
	
	act = is_write ? ACT_WRITE : ACT_READ;
	if(writeRadioPackage(dev, PRX_IO8_RW, reg_addr, act, reg_value, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s IO8[0x%x] error\n", act ? "write" : "read", reg_addr & 0xFF);
		return -1;
	}

	// set another dummy transfer to read last data, this's a wireless 'bug': the last payload is 
	// in the next package
	if(writeRadioPackage(dev, PRX_ECHO, 0, 0, 0x00, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s IO8[0x%x]\n", act ? "write" : "read", reg_addr & 0xFF);
		return -1;
	}

	if (act) {
		printf("write 0x%x to IO8[0x%x], IO8[0x%x]=0x%x\n", reg_value & 0xFF, reg_addr & 0xFFFF, reg_addr & 0xFFFF, retbuf[0]);
	} else {
		printf("read IO8[0x%x]=0x%x\n", reg_addr & 0xFF, retbuf[0] & 0xFF);
	}
	return retbuf[0];
}

int radioMem8RW(libusb_device_handle *dev, int reg_addr, int reg_value, int is_write) {
	int act;
	int pinMask;
	char retbuf[32];
	int retlen;
	
	act = is_write ? ACT_WRITE : ACT_READ;
	if(writeRadioPackage(dev, PRX_MEM8_RW, reg_addr, act, reg_value, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s MEM8[0x%x] error\n", act ? "write" : "read", reg_addr & 0xFF);
		return -1;
	}

	// set another dummy transfer to read last data, this's a wireless 'bug': the last payload is 
	// in the next package
	if(writeRadioPackage(dev, PRX_ECHO, 0, 0, 0x00, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s MEM8[0x%x] error\n", act ? "write" : "read", reg_addr & 0xFF);
		return -1;
	}

	if (act) {
		printf("write 0x%x to MEM8[0x%x], MEM8[0x%x]=0x%x\n", reg_value & 0xFF, reg_addr & 0xFF, reg_addr & 0xFF, retbuf[0]);
	} else {
		printf("read MEM8[0x%x]=0x%x\n", reg_addr & 0xFF, retbuf[0] & 0xFF);
	}
	return retbuf[0];
}

int radioConfigIO(libusb_device_handle *usb_dev, const char *strPort, int index, char *t) {
	int ret, mcuPORT, mcuPIN, mcuDDR;	
	int type;
	
	if (str2McuGpioAddr(strPort, &mcuPORT, &mcuPIN, &mcuDDR) != 0) {
		return -1;
	}
		
	type = str2McuGpioType(t);
	if (type == -1)
		return -1;
	
	ret = 0;
	switch(type) {	
	case GPIO_IPU:
		ret += radioIO8Bit(usb_dev, mcuDDR, (1 << index), 0);
		ret += radioIO8Bit(usb_dev, mcuPORT, (1 << index), 1);
		break;
	case GPIO_OUTPUT:
		ret += radioIO8Bit(usb_dev, mcuDDR, (1 << index), 1);
		break;
	case GPIO_HIZ:
		ret += radioIO8Bit(usb_dev, mcuDDR, (1 << index), 0);
		ret += radioIO8Bit(usb_dev, mcuPORT, (1 << index), 0);
		break;
	default:
		return -1;
	}
	return ret ? -1 : 0;
}

int radioCheckIO(libusb_device_handle *usb_dev, const char *strPort, int pinIndex) {
	int mcuPORT, mcuPIN, mcuDDR;
	int ret;
	
	if (str2McuGpioAddr(strPort, &mcuPORT, &mcuPIN, &mcuDDR) != 0) {
		return -1;
	}
	ret = radioIO8RW(usb_dev, mcuDDR, 0, ACT_READ);
	if(ret == -1) {
		return -1;
	}
	if(ret & (1 << pinIndex)) {
		printf("YES\n");
		return 1;
	} else {
		printf("NO\n");
		return 0;
	}
}

int radioSetIO(libusb_device_handle *dev, const char *strPort, int index, int act) {
	int mcuPort;

	if (str2McuGpioAddr(strPort, &mcuPort, NULL, NULL) < 0) {
		return -1;
	}
	
	return radioIO8Bit(dev, mcuPort, (1 << index) & 0xFF, act ? ACT_SET : ACT_CLEAR);
}

int radioGetIO(libusb_device_handle *dev, const char *strPort, int index) {
	int mcuPin;
	int buffer;
	
	if (str2McuGpioAddr(strPort, NULL, &mcuPin, NULL) < 0) {
		return -1;
	}	
	
	buffer = radioIO8RW(dev, mcuPin, 0, ACT_READ);

	if (buffer & (1 << index)) {
		printf("pin is HIGH\n");
	} else {
		printf("pin is LOW\n");
	}
	return 0;
}

int radioRFRegRW(libusb_device_handle *dev, int address, int value, int act) {
	int retlen;
	char retbuf[32];
	
	act = act ? ACT_WRITE : ACT_READ;
	
	if (writeRadioPackage(dev, PRX_RF_RW, address & 0xFF, act, value & 0xFF, NULL, 0, retbuf, &retlen) != 0) {
		printf("read RF-chip reg[0x%x] failed\n", address & 0xFF);
		return -1;
	}
	
	// set another dummy transfer to read last data, this's a wireless 'bug': the last payload is 
	// in the next package
	if(writeRadioPackage(dev, PRX_ECHO, 0, 0, 0x00, NULL, 0, retbuf, &retlen) != 0) {
		printf("read RF-chip reg[0x%x] failed\n", address & 0xFF);
		return -1;
	}
	
	if (retlen != 1) {
		printf("read RF-chip reg[0x%x] response error\n", address & 0xFF);
		return -1;
	}
	printf("RF-chip reg[0x%x]=0x%x\n", address & 0xFF, retbuf[0] & 0xFF);
	return 0;
}

int radioRFRegBit(libusb_device_handle *dev, int address, int index, int act) {
	int retlen;
	char retbuf[32];
	
	act = act ? ACT_SET : ACT_CLEAR;
	
	if (writeRadioPackage(dev, PRX_RF_BIT, address & 0xFF, act, (1 << index), NULL, 0, retbuf, &retlen) != 0) {
		printf("%s RF-chip reg[0x%x] failed\n", act ? "set" : "clear", address & 0xFF);
		return -1;
	}
	
	// set another dummy transfer to read last data, this's a wireless 'bug': the last payload is 
	// in the next package
	if(writeRadioPackage(dev, PRX_ECHO, 0, 0, 0x00, NULL, 0, retbuf, &retlen) != 0) {
		printf("%s RF-chip reg[0x%x] failed\n", act ? "set" : "clear", address & 0xFF);
		return -1;
	}
	
	if (retlen != 1) {
		printf("%s RF-chip reg[0x%x] response error\n", act ? "set" : "clear", address & 0xFF);
		return -1;
	}
	printf("%s RF-chip reg[0x%x] bit%d, reg[0x%x]=0x%x\n", act?"set":"clear", address & 0xFF, index, address & 0xFF, retbuf[0] & 0xFF);
	return 0;
}

int radioReset(libusb_device_handle *dev) {
	int retlen;
	int keyword = 0x8579;
	
	if(writeRadioPackage(dev, PRX_RESET, keyword, 0, 0, NULL, 0, NULL, &retlen) != 0) {
		printf("remote device reset error\n");
		return -1;
	} else {
		printf("remote device reset ok\n");
	}
	return 0;
}

int str2McuGpioType(const char *c) {
	strtolower((char *)c);

	if ( (strcmp(c, "hiz") == 0) || (strcmp(c, "h") == 0) ) {
		return GPIO_HIZ;
	} else if ( (strcmp(c, "input") == 0) || (strcmp(c, "i") == 0) ) {
		return GPIO_IPU;
	} else if ( (strcmp(c, "output") == 0) || (strcmp(c, "o") == 0) ) {
		return GPIO_OUTPUT;
	} else {
		printf("unkonwn pin mode, use 'input', 'output' or 'hiz'\n");
		return -1;
	}
	return 0;
}

int str2McuGpioAddr(const char *c, int *portRegAddr, int *pinRegAddr, int *ddrRegAddr) {
	strtolower((char *)c);
	
	if ( (strcmp(c, "pioa") == 0) || (strcmp(c, "a") == 0) ) {
		_LOAD_ADDR(portRegAddr, pinRegAddr, ddrRegAddr, A);
	} else if ( (strcmp(c, "piob") == 0) || (strcmp(c, "b") == 0) ) {
		_LOAD_ADDR(portRegAddr, pinRegAddr, ddrRegAddr, B);
	} else if ( (strcmp(c, "pioc") == 0) || (strcmp(c, "c") == 0) ) {
		_LOAD_ADDR(portRegAddr, pinRegAddr, ddrRegAddr, C);
	} else if ( (strcmp(c, "piod") == 0) || (strcmp(c, "d") == 0) ) {
		_LOAD_ADDR(portRegAddr, pinRegAddr, ddrRegAddr, D);
	} else {
		return -1;
	}
	return 0;
}

static void strtolower(char *str) {
	int i;
	
	for(i = 0; i < strlen(str); i ++) {
		str[i] = tolower(str[i]);
	}
}

#if (1) 
unsigned long getTickCount() {
	struct timespec ts;
	
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
#else
unsigned long getTickCount() {
	return 0;
}
#endif
